package jmetal.metaheuristics.grdea;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import jmetal.core.Algorithm;
import jmetal.core.Operator;
import jmetal.core.Problem;
import jmetal.core.Solution;
import jmetal.core.SolutionSet;
import jmetal.metaheuristics.grdea.utils.DynamicUtils;
import jmetal.util.GridRanking;
import jmetal.util.JMException;
import jmetal.util.PermutationUtility;
import jmetal.util.Ranking;

@SuppressWarnings("serial")
public class GrdEA extends Algorithm {

	private int populationSize;
	private int maxEvaluations;
    private Operator mutationOperator;
    private Operator crossoverOperator;
    private Operator selectionOperator;
    
    private double[] idealPoint;
    private double[] nadirPoint;
    
    private int numberOfGrids;
    private double[] unitLen;
    
    private int evaluateNumber;
    
    private final static int[] gridNumbers = {0,0,200,20,8,5,3,3};
    private final static int cycleLength = 50;
    
	public GrdEA(Problem problem){
		super(problem);
	}

	@Override
	public SolutionSet execute() throws JMException, ClassNotFoundException {
		initializeAlgorithm();
		SolutionSet population = getInitializePopulation();
		int itr = 0;
//		SolutionSet nonDominate = new Ranking(population).getSubfront(0);
//		externalPopulation.addAll(nonDominate, 0, nonDominate.size());
//		setIdealPoint(nonDominate);
//		setNadirPoint(nonDominate);
		int cycle = 0;
		while (evaluateNumber < maxEvaluations) {
			SolutionSet offspring = getOffSpring(population); 
			SolutionSet union = population.union(offspring);
			Ranking rank = new Ranking(union);
			setIdealPoint(rank.getSubfront(0));
			setNadirPoint(rank.getSubfront(0));
			SolutionSet inBoundsSet = new SolutionSet(union.size());
			SolutionSet outBoundsSet = new SolutionSet(union.size());
			getInAndOutBounds(rank,inBoundsSet,outBoundsSet);
			population.clear();
			population.addAll(inBoundsSet, 0, inBoundsSet.size());
			if(population.size() < populationSize){
				int remainSize = populationSize - population.size();
				SolutionSet outRemainSet = getRemainSolutionByMinDistance(outBoundsSet,remainSize);
				population.addAll(outRemainSet,0,outRemainSet.size());
			}else if(population.size() > populationSize){
				setGridEnvironment(population);
				//pareto dominance
				Ranking gridRank = new Ranking(population);
				population.clear();
				int rankIndex = 0;
				int remain = populationSize;
				while(gridRank.getSubfront(rankIndex).size() < remain){
					SolutionSet front =  gridRank.getSubfront(rankIndex);
					population.addAll(front, 0, front.size());
					rankIndex++;
					remain -= front.size();
				}
				SolutionSet lastFront = gridBasedSelection(gridRank.getSubfront(rankIndex),remain);
				population.addAll(lastFront, 0, lastFront.size());
			}
//			population.printObjectivesToFile("results/aatest/FUN"+itr);
			System.out.println(++itr + " iteration...");
			
			if(itr % cycleLength==0){
				redefined(population, itr);
				cycle = itr/cycleLength;
			}
			String probName = problem_.getClass().getSimpleName();
			String root = "output_data/process/gplsd/"+probName+"/"+cycleLength+"len/"+cycle+"cycle/";
			File folder = new File(root);
			if(!folder.exists() || !folder.isDirectory()){
				folder.mkdirs();
			}
			population.printObjectivesToFile(root+"FUN"+ itr % cycleLength );
		}
		population.printCoordinateToFile("output_data/FUN-coordinate");
		return new Ranking(population).getSubfront(0);
	}

	/**
	 * 问题动态变换设置
	 * @param population
	 * @param itr
	 */
	private void redefined(SolutionSet population, int itr) {
		try {
			problem_ = DynamicUtils.reDefinedProblem(population, problem_, itr / cycleLength % 10);
		} catch (Exception e) {
			System.out.println("Redefine problem error!");
			e.printStackTrace();
			System.exit(0);
		}
		int objNum = problem_.getNumberOfObjectives() ; 
		numberOfGrids = gridNumbers[objNum];
		idealPoint = new double[objNum];
		nadirPoint = new double[objNum];
		unitLen = new double[objNum];
	}

	private SolutionSet getRemainSolutionByMinDistance(SolutionSet outBoundsSet, int remainSize) {
		SolutionSet remainSet = new SolutionSet(remainSize);
		for(int i = 0;i<remainSize;i++){
			int minIdx = i;
			double minMaxDis = maxDis(outBoundsSet.get(i));
			
			for(int j = i+1;j<outBoundsSet.size();j++){
				Solution compaSolution = outBoundsSet.get(j);
				if(maxDis(compaSolution) < minMaxDis){
					minIdx = j;
					minMaxDis = maxDis(compaSolution);
				}
			}
			//swap
			Solution tempSolution = outBoundsSet.get(i);
			outBoundsSet.set(i, outBoundsSet.get(minIdx));
			outBoundsSet.set(minIdx, tempSolution);
			
			remainSet.add(outBoundsSet.get(i));
		}
		return remainSet;
	}

	private double maxDis(Solution solution) {
		double maxDis = Double.NEGATIVE_INFINITY;
		for(int i = 0;i<solution.getNumberOfObjectives();i++){
			if(solution.getObjective(i)-nadirPoint[i] > maxDis){
				maxDis = solution.getObjective(i)-nadirPoint[i];
			}
		}
		return maxDis;
	}

	private void getInAndOutBounds(Ranking rank, SolutionSet inBoundsSet, SolutionSet outBoundsSet) {
		for(int i = 0;i<rank.getNumberOfSubfronts();i++){
			SolutionSet front = rank.getSubfront(i);
			for(int j = 0;j<front.size();j++){
				if(!isOutOfBounds(front.get(j))){
					inBoundsSet.add(front.get(j));
				}else {
					outBoundsSet.add(front.get(j));
				}
			}
		}

	}

	private SolutionSet getOffSpring(SolutionSet population) throws JMException {
		SolutionSet offspring = new SolutionSet(population.size());
		Solution[] parents = new Solution[2];
		for (int i = 0; i < (population.size() / 2); i++) {
			if (evaluateNumber < maxEvaluations) {
				parents[0] = (Solution) selectionOperator.execute(population);
				parents[1] = (Solution) selectionOperator.execute(population);
//				parents[0] = population.get(permutations[idx++]);
//				parents[1] = population.get(permutations[idx++]);
				Solution[] offSpring = (Solution[]) crossoverOperator.execute(parents);
				mutationOperator.execute(offSpring[0]);
				mutationOperator.execute(offSpring[1]);
				problem_.evaluate(offSpring[0]);
				problem_.evaluateConstraints(offSpring[0]);
				problem_.evaluate(offSpring[1]);
				problem_.evaluateConstraints(offSpring[1]);
				offspring.add(offSpring[0]);
				offspring.add(offSpring[1]);
				evaluateNumber += 2;
			} 
		}
		return offspring;
	}


	private boolean isOutOfBounds(Solution solution) {
		for(int i = 0;i<solution.getNumberOfObjectives();i++){
			if(solution.getObjective(i)>nadirPoint[i]){
				return true;
			}
		}
		return false;
	}

	
	private SolutionSet gridBasedSelection(SolutionSet population, int remain) {
		SolutionSet permSet = new SolutionSet(population.size());
		int[] perm = new PermutationUtility().intPermutation(population.size());
		for(int i = 0;i<perm.length;i++){
			permSet.add(population.get(perm[i]));
		}
		
		for(int i = 0;i<permSet.size();i++){
			Solution solution = permSet.get(i);
			for(int j = permSet.size()-1;j > i;j--){
 				if(isEqualOnGrid(solution, permSet.get(j))){
					if(fitnessFunction(solution)>fitnessFunction(permSet.get(j))){
						permSet.set(i, permSet.get(j));
					}
					permSet.remove(j);
					if(permSet.size() <= remain)
						return permSet;
				}
			}
		}
//		if(permSet.size()>remain){
//			SolutionSet solutionSet = new SolutionSet(remain);
//			GridRanking gridRanking = new GridRanking(permSet);
//			int vernier = remain;
//			int rankIndex = 0;
//			SolutionSet front ;
//			while(gridRanking.getSubfront(rankIndex).size()<vernier){
//				front = gridRanking.getSubfront(rankIndex);
//				solutionSet.addAll(front, 0, front.size());
//				rankIndex++ ;
//				vernier -= front.size();
//			}
//			front = gridRanking.getSubfront(rankIndex);
//			for(int i = 0;i<vernier;i++){
//				solutionSet.add(front.get(i));
//			}
//			permSet = solutionSet;
//			
//		}
		return permSet;
	}

	/**
	 * fitness function
	 * @param solution
	 * @return
	 */
	private double fitnessFunction(Solution solution) {
		double sum = 0;
		for(int i = 0;i<solution.getNumberOfObjectives();i++){
			int coordinate = solution.getGridCoordinate(i);
			double coef = 1.0/(double)(coordinate + 0.00001);
			sum += coef * (solution.getObjective(i) - (idealPoint[i]+coordinate * unitLen[i]));
		}
		return sum;
	}

	/**
	 * if two solution have the same grid
	 * @param solution
	 * @param solution2
	 * @return
	 */
	private boolean isEqualOnGrid(Solution solution, Solution solution2) {
		for(int i = 0;i<solution.getNumberOfObjectives();i++){
			if(solution.getGridCoordinate(i)!=solution2.getGridCoordinate(i)){
				return false;
			}
		}
		return true;
	}

	/**
	 * set grid for population
	 * @param population
	 */
	private void setGridEnvironment(SolutionSet population) {
		for(int i = 0;i<unitLen.length;i++){
			unitLen[i] = (nadirPoint[i] - idealPoint[i])/(double) numberOfGrids ;
		}
		for(int i = 0;i<population.size();i++){
			Solution solution = population.get(i);
			setCoordinate(solution);
		}
	}

	/**
	 * set coordinate for solution
	 * @param solution
	 */
	private void setCoordinate(Solution solution) {
		for(int i = 0;i<solution.getNumberOfObjectives();i++){
			int coorValue = (int) ((solution.getObjective(i)-idealPoint[i]) / unitLen[i]);
			solution.setGridCoordinate(i, coorValue);
		}
	}

	private void setIdealPoint(SolutionSet population) {
//		for(int i = 0;i<problem_.getNumberOfObjectives();i++){
//			idealPoint[i] = Double.MAX_VALUE;
//		}
//		for(int i = 0;i<population.size();i++){
//			updateIdealPoint(population.get(i));
//		}
		for(int i = 0;i<problem_.getNumberOfObjectives();i++){
			idealPoint[i] = -0.1;
		}
		
	}

	private void updateIdealPoint(Solution solution) {
		for(int i = 0;i<solution.getNumberOfObjectives();i++){
			if(solution.getObjective(i)<idealPoint[i]){
				idealPoint[i] = solution.getObjective(i);
			}
		}
		
	}

	private void setNadirPoint(SolutionSet population) {
//		for(int i = 0;i<problem_.getNumberOfObjectives();i++){
//			nadirPoint[i] = Double.NEGATIVE_INFINITY;
//		}
//		for(int i = 0;i<population.size();i++){
//			updateNadirPoint(population.get(i));
//		}
		
		for(int i = 0;i<problem_.getNumberOfObjectives();i++){
			nadirPoint[i] = 1.1;
		}
		
	}

	private void updateNadirPoint(Solution solution) {
		for(int i = 0;i<solution.getNumberOfObjectives();i++){
			if(solution.getObjective(i)>nadirPoint[i]){
				nadirPoint[i] = solution.getObjective(i);
			}
		}
	}

	private SolutionSet getInitializePopulation() throws ClassNotFoundException, JMException {
		SolutionSet population = new SolutionSet(populationSize);
		for(int i = 0;i<populationSize;i++){
			Solution solution = new Solution(this.problem_);
			this.problem_.evaluate(solution);
			evaluateNumber++;
			population.add(solution);
			
		}
		return population;
	}

	private void initializeAlgorithm() {
	    populationSize = ((Integer) getInputParameter("populationSize")).intValue();
	    maxEvaluations = ((Integer) getInputParameter("maxEvaluations")).intValue();
	    numberOfGrids = ((Integer) getInputParameter("numberOfGrids")).intValue();
	    mutationOperator = operators_.get("mutation");
	    crossoverOperator = operators_.get("crossover");
	    selectionOperator = operators_.get("selection");
	    evaluateNumber = 0;
	    idealPoint = new double[this.problem_.getNumberOfObjectives()];
	    nadirPoint = new double[this.problem_.getNumberOfObjectives()];
	    unitLen = new double[this.problem_.getNumberOfObjectives()];
	}
	

}
